﻿using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Transactions;

namespace O1.IO.Storage
{
    public class ConcurrentWriter : IEnlistmentNotification
    {
        private const long Infinity = long.MaxValue;

        private byte[] nextVersion;

        private byte[] previousVersion;
        
        private long lockValue;

        private Transaction currentTransaction;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] SpinWrite(byte[] data)
        {
            // TODO: this class must evolve to handle direct access to storage.
            // This class must become version aware (read time version and valid visibility ranges >= begin, < end)
            // This class must modify begin version on every write.
            // This class will fail write - write conflicts
            // This class will know the current transaction ID and what operation is happening
            // Aborting a tx means do nothing. Do not checkpoint recovery or invalidate if possible.
            this.AcquireLock();
            var previous = this.nextVersion;
            this.nextVersion = data;
            if (this.currentTransaction == null)
            {
                this.lockValue = 0;
            }
            else
            {
                this.previousVersion = this.nextVersion;
            }

            return previous;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] SpinRead()
        {
            if (Interlocked.CompareExchange(ref this.lockValue, 1, 0) != 0)
            {
                var wait = new SpinWait();
                while (Interlocked.CompareExchange(ref this.lockValue, 1, 0) != 0)
                {
                    wait.SpinOnce();
                }
            }

            try
            {
                return this.nextVersion;
            }
            finally
            {
                this.lockValue = 0;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] DirtyRead()
        {
            return this.nextVersion;
        }

        public void Commit(Enlistment enlistment)
        {
            this.AcquireLock();
            this.previousVersion = null;
            this.ReleaseLock();
            Thread.MemoryBarrier();
            enlistment.Done();
        }

        public void InDoubt(Enlistment enlistment)
        {
        }

        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            this.AcquireLock();
            Thread.MemoryBarrier();
            preparingEnlistment.Prepared();
        }

        public void Rollback(Enlistment enlistment)
        {
            this.AcquireLock();
            this.nextVersion = this.previousVersion;
            this.previousVersion = null;
            this.ReleaseLock();
            Thread.MemoryBarrier();
            enlistment.Done();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void AcquireLock()
        {
            if (Interlocked.CompareExchange(ref this.lockValue, 1, 0) != 0)
            {
                if (Transaction.Current != null &&
                ReferenceEquals(Transaction.Current, this.currentTransaction))
                {
                    return;
                }

                var wait = new SpinWait();
                while (Interlocked.CompareExchange(ref this.lockValue, 1, 0) != 0)
                {
                    wait.SpinOnce();
                }
            }

            this.currentTransaction = Transaction.Current;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReleaseLock()
        {
            this.lockValue = 0;
            this.currentTransaction = null;
        }
    }
}
